package com.example.springsecuritycsrf.config;

import com.alibaba.fastjson.JSON;
import com.example.springsecuritycsrf.domain.dto.LoginUser;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;

@Slf4j
@Component
public class CsrfTokenRedisRepository implements CsrfTokenRepository {
    public static final String CSRF_PARAMETER_NAME = "_csrf";
    public static final String CSRF_HEADER_NAME = "X-CSRF-TOKEN";

    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    private LoginUser loginUser;
    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        String csrfToken = StringUtils.replace(UUID.randomUUID().toString(),"_",StringUtils.EMPTY);
        log.debug("csrf filter: redis csrf token repository: generate token: {}", csrfToken);
        return new DefaultCsrfToken(CSRF_HEADER_NAME,CSRF_PARAMETER_NAME,csrfToken);
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        String redisKey = "user_" + loginUser.getUsername();
        String csrfToken = token.getToken();
        redisTemplate.opsForList().leftPush(redisKey,csrfToken);
        response.setHeader("csrf-token",token.getToken());
    }

    @SneakyThrows
    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        LoginUser loginUser = (LoginUser) Optional.ofNullable(JSON.parseObject(request.getInputStream(), StandardCharsets.UTF_8,LoginUser.class))
                        .orElseThrow(() -> new AuthenticationServiceException("Login User is null"));
        this.loginUser = loginUser;
        log.debug("csrf filter: redis csrf token repository: load token by LoginUser info ({}).", loginUser.toString());

        final String csrfToken = getCachedToken();
        if (StringUtils.isBlank(csrfToken)) {
            return null;
        }
        return new DefaultCsrfToken(CSRF_HEADER_NAME,CSRF_PARAMETER_NAME,csrfToken);
    }

    private String getCachedToken(){
        String redisKey = "user_" + loginUser.getUsername();
        final String csrfToken = redisTemplate.opsForList().leftPop(redisKey);
        if (StringUtils.isEmpty(csrfToken)) {
            return StringUtils.EMPTY;
        }
        return csrfToken;
    }
}
